(function() {
	/**
	 * Mouse button mapping
	 * @param button {integer} client button number
	 */
	function mouseButtonMap(button) {
		switch(button) {
		case 0:
			return 1;
		case 2:
			return 2;
		default:
			return 0;
		}
	};
	
	/**
	 * Mstsc client
	 * Input client connection (mouse and keyboard)
	 * bitmap processing
	 * @param canvas {canvas} rendering element
	 */
	function Client(canvas) {
		this.canvas = canvas;
		// create renderer
		this.render = new Mstsc.Canvas.create(this.canvas); 
		this.socket = null;
		this.activeSession = false;
		this.install();
	}
	
	Client.prototype = {
		install : function () {
			var self = this;
			// bind mouse move event
			this.canvas.addEventListener('mousemove', function (e) {
				if (!self.socket) return;
				
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, 0, false);
				e.preventDefault || !self.activeSession();
				return false;
			});
			this.canvas.addEventListener('mousedown', function (e) {
				if (!self.socket) return;
				
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), true);
				e.preventDefault();
				return false;
			});
			this.canvas.addEventListener('mouseup', function (e) {
				if (!self.socket || !self.activeSession) return;
				
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), false);
				e.preventDefault();
				return false;
			});
			this.canvas.addEventListener('contextmenu', function (e) {
				if (!self.socket || !self.activeSession) return;
				
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), false);
				e.preventDefault();
				return false;
			});
			this.canvas.addEventListener('DOMMouseScroll', function (e) {
				if (!self.socket || !self.activeSession) return;
				var isHorizontal = false;
				var delta = e.detail;
				var step = 150; //Math.round(Math.abs(delta) * 15 / 8);
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('wheel', e.clientX - offset.left, e.clientY - offset.top, step, delta > 0, isHorizontal);
				e.preventDefault();
				return false;
			});

			let mousewheel_timeout = 0
			this.canvas.addEventListener('mousewheel', function (e) {
				if (!self.socket || !self.activeSession) return;
				if (mousewheel_timeout > Date.now()) {
					e.preventDefault();
					return false;
				}
				mousewheel_timeout = Date.now() + 100;

				var isHorizontal = Math.abs(e.deltaX) > Math.abs(e.deltaY);
				var delta = isHorizontal ? e.deltaX: e.deltaY;
				var step = 150; //Math.round(Math.abs(delta) * 15 / 8);
				var offset = Mstsc.elementOffset(self.canvas);
				self.socket.emit('wheel', e.clientX - offset.left, e.clientY - offset.top, step, delta > 0, isHorizontal);
				e.preventDefault();
				return false;
			});
			
			// bind keyboard event
			window.addEventListener('keydown', function (e) {
				if (!self.socket || !self.activeSession) return;
				self.socket.emit('scancode', Mstsc.scancode(e), true);
				e.preventDefault();
				return false;
			});
			window.addEventListener('keyup', function (e) {
				if (!self.socket || !self.activeSession) return;
				self.socket.emit('scancode', Mstsc.scancode(e), false);
				e.preventDefault();
				return false;
			});

			const handleFocus = async() =>{
				if (!self.socket || !self.activeSession) return
				if(!document){ return }
				if(!navigator || !navigator.clipboard){ return }
				const text = await navigator.clipboard.readText()
				self.socket.emit('clipboard', text);
			}
			
			window.addEventListener('focus', handleFocus);
			document.addEventListener('visibilitychange', () => {
					if (document.visibilityState === 'visible') {
							handleFocus();
					}
			});
			return this;
		},
		connect : function (data, next) {
			var parts = document.location.pathname.split('/')
		  var base = parts.slice(0, parts.length - 1).join('/') + '/'
		  var path = base + 'socket.io'
			var self = this;
			this.socket = io(window.location.protocol + "//" + window.location.host, { "path": path })
				.on('rdp-connect', function() {
					console.log('[mstsc.js] connected');
					self.activeSession = true;
				}).on('rdp-bitmap', bitmap=>{
					self.render.update(bitmap)
				}).on('rdp-bitmaps', bitmaps=>{
					for (const bitmap of bitmaps) {
						self.render.update(bitmap)
					}
				}).on('rdp-clipboard', function (content) {
					console.log('[mstsc.js] clipboard', content)
					navigator.clipboard.writeText(content)
				}).on('rdp-close', function() {
					next(false, null);
					console.log('[mstsc.js] close');
					self.activeSession = false;
				}).on('rdp-error', function (err) {
					next(false, err);
					console.log('[mstsc.js] error : ' + err.code + '(' + err.message + ')');
					self.activeSession = false;
				});

			const searchParams = new URLSearchParams(location.search);
			const paramsObject = {};
			for (let [key, value] of searchParams.entries()) {
					paramsObject[key] = value;
			}
			paramsObject.updateLimit = paramsObject.updateLimit || paramsObject.limit || paramsObject.rate || 100
			if(paramsObject.updateLimit){
				paramsObject.updateLimit = parseInt(paramsObject.updateLimit)
				if(isNaN(paramsObject.updateLimit)){
					paramsObject.updateLimit = 100
				}
				if(paramsObject.updateLimit < 1){
					paramsObject.updateLimit = 100
				}
				if(paramsObject.updateLimit > 2000){
					paramsObject.updateLimit = 2000
				}
			}

			const ip = data.ip
			this.socket.emit('infos', {
				ip : ip.indexOf(":")>-1 ? ip.split(":")[0] : ip,
				port : ip.indexOf(":")>-1 ? parseInt(ip.split(":")[1]) : 3389,
				username: data.username,
				password : data.password,
				screen : { 
					width : this.canvas.width, 
					height : this.canvas.height 
				},
				updateLimit : paramsObject.updateLimit || 100,
				locale : Mstsc.locale()
			})
		},
		connecttoid : function (id, next) {
			var parts = document.location.pathname.split('/')
		  var base = parts.slice(0, parts.length - 1).join('/') + '/'
		  var path = base + 'socket.io'
			var self = this;
			this.socket = io(window.location.protocol + "//" + window.location.host, { "path": path })
				.on('rdp-connect', function() {
					console.log('[mstsc.js] connected');
					self.activeSession = true;
				}).on('rdp-bitmap', bitmap=>{
					self.render.update(bitmap)
				}).on('rdp-bitmaps', bitmaps=>{
					for (const bitmap of bitmaps) {
						self.render.update(bitmap)
					}
				}).on('rdp-clipboard', function (content) {
					console.log('[mstsc.js] clipboard', content)
					navigator.clipboard.writeText(content)
				}).on('rdp-close', function() {
					next(false, null);
					console.log('[mstsc.js] close');
					self.activeSession = false;
				}).on('rdp-error', function (err) {
					next(false, err);
					console.log('[mstsc.js] error : ' + err.code + '(' + err.message + ')');
					self.activeSession = false;
				});

			const searchParams = new URLSearchParams(location.search);
			const paramsObject = {};
			for (let [key, value] of searchParams.entries()) {
					paramsObject[key] = value;
			}
			paramsObject.updateLimit = paramsObject.updateLimit || paramsObject.limit || paramsObject.rate || 100
			if(paramsObject.updateLimit){
				paramsObject.updateLimit = parseInt(paramsObject.updateLimit)
				if(isNaN(paramsObject.updateLimit)){
					paramsObject.updateLimit = 100
				}
				if(paramsObject.updateLimit < 1){
					paramsObject.updateLimit = 100
				}
				if(paramsObject.updateLimit > 2000){
					paramsObject.updateLimit = 2000
				}
			}

			this.socket.emit('connectid', {
				id: id,
				screen : { 
					width : this.canvas.width, 
					height : this.canvas.height 
				},
				updateLimit : paramsObject.updateLimit || 100,
				locale : Mstsc.locale()
			})
		}
	}
	
	Mstsc.client = {
		create : function (canvas) {
			return new Client(canvas);
		}
	}
})();
